/*
    This file is part of Posixcpp library.

    Posixcpp library is free software: you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation, either version 3 of
    the License, or (at your option) any later version.

    Posixcpp library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU LesserGeneral Public
    License along with Posixcpp library. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include "posix_io_handle.hpp"

#include <cassert>

using namespace posix::io;

const unsigned posix::io::DEFAULT_RECEIVETRIES = 15;
const unsigned posix::io::DEFAULT_SENDTRIES = 15;

Handle::Handle ( BlockingMode_t bMode ) :
m_descriptor(-1),
m_readTries(DEFAULT_RECEIVETRIES),
m_sendTries(DEFAULT_SENDTRIES),
m_blockingMode(bMode)
{

}

Handle::Handle ( int descriptor ) :
m_descriptor(descriptor),
m_readTries(DEFAULT_RECEIVETRIES),
m_sendTries(DEFAULT_SENDTRIES),
m_blockingMode(BlockingMode_t::NON_BLOCK)
{

}

Handle::~Handle ( )
{

}

void Handle::close ( ) {
    PRECONDITION_ASSERT( m_descriptor>=0, "node is invalid");

	if (m_descriptor >= 0) {
		POSIX_CALL_ASSERT_X( ::close(m_descriptor) != -1,
						 "Unable to close the node" )
		m_descriptor = -1;
	}
}

void Handle::read(
  posixcpp::ByteArray & data,
  IOMode_t mode,
  time::Timespec timeout )
{
	PRECONDITION_ASSERT( !data.isEmpty(), "data is empty");
	PRECONDITION_ASSERT( m_descriptor > 0, "file isn't open");

	// Number of bytes have been read
    ssize_t count;

	if (m_descriptor <= 0)
		throw std::runtime_error("File isn't open");

	// Single read mode is set
	if (mode == IOMode_t::SINGLE) {
		count = ::read(m_descriptor, data.data(), data.size());
		// Read was success
		if (count > 0) {
			POSTCONDITION_ASSERT(size_t(count) <= data.size(),
					"Too much data was read");
			// Read required data size
			if (size_t(count) == data.size())
				return;
			//Read less then required
			else if (size_t(count) < data.size())
				data.resize(count);
		}
		// Error while reading
		else
			// There is no data to read
			if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (count == 0))
				data.resize(0);
			// Other error
			else
				throw std::system_error( errno, std::system_category(),
						"Unable to read specified data");
	}
	// Cyclic read mode is set
	else {
		//Remaining read tries
		ssize_t receiveTries = m_readTries;
		// Read data size
		size_t total = 0UL;
		// I/O events
		Events src, res;
		do {
			count = ::read(m_descriptor, data.data()+total, data.size()-total);
			// If success reqading
			if (count > 0) {
				total += count;
				POSTCONDITION_ASSERT(total <= data.size(), "Too much data was read");
			}
			// On error
			else if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (count == 0)) {
					src.setFlag(Events_t::INPUT);
					res = this->poll(src, timeout);
			}
			else
					throw std::system_error(errno,
						std::system_category(), "Unable to read specified data");
        } while( (total < data.size()) && (receiveTries-- > 0) );

        if (total < data.size())
            data.resize(total);
	}
}

ssize_t Handle::write(
  const posixcpp::ByteArray & data,
  IOMode_t mode,
  time::Timespec timeout )
{
    PRECONDITION_ASSERT(!data.isNull(), "data is null");
    PRECONDITION_ASSERT(data.size() > 0, "data is empty");
    PRECONDITION_ASSERT(m_descriptor > 0, "file isn't open");

    // Write bytes count
    ssize_t count;

    if (m_descriptor <= 0)
        throw std::runtime_error("File isn't opened");
    // Single write mode is set
    if (mode == IOMode_t::SINGLE) {
		count = ::write(m_descriptor, data.data(), data.size());
		// Write was success
		if (count >= 0)
            return count;
		else
			throw std::system_error(errno, std::system_category(),
					"Unable to write specified data");
	}
	else {
        // Write tries remaining
        ssize_t sendTries = m_sendTries;
        // Total write count
		size_t total = 0;
	    do {
			count = ::write(m_descriptor, data.data()+total, data.size()-total);
            if (count > 0) {
				total += count;
				assert(total <= data.size());
			}
	        else if ( (errno == EAGAIN) || (errno == EWOULDBLOCK) || (count == 0)) {
					Events src, res;
					src.setFlag(Events_t::OUTPUT);
					res = this->poll(src, timeout);
			}
			else
				throw std::system_error(errno, std::system_category(),
						"Unable to write specified data");
	    } while ( (total < data.size()) && (sendTries-- > 0) );
	}
	return count;
}

Events Handle::poll(Events events, time::Timespec timeout)
{
    PRECONDITION_ASSERT( m_descriptor > 0, "file isn't open" );

	Events result;
	int tOut = int64_t(timeout);
	if (tOut > 0)
		tOut /= 1000;
	else if (tOut < 0)
		tOut = -1;

	pollfd thisFd = {m_descriptor, events.data(), 0};
    POSIX_CALL_ASSERT_X( ::poll(&thisFd, 1, tOut) != -1,
					"Error while waiting for events on the file");
	result.data() = thisFd.revents;

	return result;
}
